package io.gitee.hejiang.action.impl;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySourcesPropertyResolver;

import com.fasterxml.jackson.databind.JavaType;

import io.gitee.hejiang.action.SwaggerSqlAction;
import io.gitee.hejiang.action.SwaggerSqlActionProvider;
import io.swagger.inflector.models.RequestContext;

public class CommandActionProvider implements SwaggerSqlActionProvider {
	private static Logger _logger = LoggerFactory.getLogger(CommandAction.class);

	@Override
	public SwaggerSqlAction createAction(Map<String, Object> config) {
		String type = (String) config.get("type");
		if ("command".equalsIgnoreCase(type) || config.containsKey("command")) {
			return new CommandAction();
		}
		return null;
	}

	static class CommandAction implements SwaggerSqlAction {
		SwaggerSqlAction _next;
		String _key;
		Map<String, Object> _config;
		private String _command;
		private String _wd;
		private List<String> _arguments;
		private Integer _timeout;

		@SuppressWarnings("unchecked")
		@Override
		public void init(Map<String, Object> config, Map<String, JavaType> argumentTypes, SwaggerSqlAction next)
				throws Exception {
			_next = next;
			_key = (String) config.get("key");
			if (_key == null) {
				_key = "ans";
			}

			_config = config;
			_command = (String) config.get("command");
			_wd = (String) config.get("wd");
			_arguments = (List<String>) config.get("arguments");
			_timeout = (Integer) config.get("timeout");
		}

		@Override
		public Object execute(RequestContext request, Connection connection, Map<String, Object> arguments,
				Map<String, Object> context) throws Exception {
			// 获取实际命令行
			MutablePropertySources sources = new MutablePropertySources();
			sources.addFirst(new CommandPropertySource(arguments, context));

			List<String> command = new ArrayList<>();
			if (_command != null) {
				command.add(_command);
			}
			if (_arguments != null) {
				command.addAll(_arguments);
			}

			if (command.size() == 0) {
				throw new Exception("请通过 command 或 arguments 指定命令行参数");
			}

			PropertySourcesPropertyResolver resolver = new PropertySourcesPropertyResolver(sources);
			for (int i = 0; i < command.size(); ++i) {
				command.set(i, resolver.resolvePlaceholders(command.get(i)));
			}

			// 执行命令行
			String cl = String.join(" ", command);
			_logger.info("Execute command \"{}\"...", cl);
			ProcessBuilder builder = new ProcessBuilder(command);
			if (_wd != null) {
				builder.directory(new File(_wd));
			}
			builder.redirectErrorStream(true);

			Process proc = builder.start();
			long to = System.currentTimeMillis() + (_timeout != null ? _timeout : 60) * 1000;

			BufferedReader bufferedReader = new BufferedReader(
					new InputStreamReader(proc.getInputStream(), Charset.defaultCharset()));
			String line;
			while ((line = bufferedReader.readLine()) != null) {
				_logger.info(line);
			}

			while (proc.isAlive() && to > System.currentTimeMillis()) {
				Thread.sleep(100);
			}

			int ret = -1;
			if (proc.waitFor(0, TimeUnit.MILLISECONDS)) {
				ret = proc.exitValue();
				_logger.info("Command \"{}\" exited: {}", cl, ret);
			} else {
				_logger.error("Command \"{}\" timeout.", cl);
				proc.destroy();
			}

			// 链式执行
			if (_next == null) {
				return ret;
			} else {
				context.put(_key, ret);
				return _next.execute(request, connection, arguments, context);
			}
		}
	}

	// 自定义属性源
	static class CommandPropertySource extends PropertySource<Object> {
		Map<String, Object> arguments;
		Map<String, Object> context;

		public CommandPropertySource(Map<String, Object> arguments, Map<String, Object> context) {
			super("CommandPropertySource");
			this.arguments = arguments;
			this.context = context;
		}

		@Override
		public Object getProperty(String name) {
			if (context.containsKey(name)) {
				return context.get(name);
			}

			if (arguments.containsKey(name)) {
				return arguments.get(name);
			}

			return null;
		}
	}
}